Skip to content

Comments

feat:implement user dashboard-my claims#87

Merged
Benjtalkshow merged 3 commits intoboundlessfi:mainfrom
KingFRANKHOOD:main
Feb 21, 2026
Merged

feat:implement user dashboard-my claims#87
Benjtalkshow merged 3 commits intoboundlessfi:mainfrom
KingFRANKHOOD:main

Conversation

@KingFRANKHOOD
Copy link
Contributor

@KingFRANKHOOD KingFRANKHOOD commented Feb 19, 2026

Implement User Dashboard – My Claims Closes #83

Dashboard section for tracking claimed work.

TASKS

  • Correct filtering by status
  • Links to opportunity detail

Implementations

  • Implemented data type and created all required sections - Active Claims, In Review, Completed
  • Status-based filtering is implemented and tested (including normalization)
  • Opportunity detail links are implemented via /bounty/{id} from the My Claims cards.
  • All implementations were properly tested

Screenshot

Screenshot from 2026-02-20 23-58-32

Summary by CodeRabbit

  • New Features

    • Added "My Claims" tab in user profiles displaying bounty claims organized by status (Active Claims, In Review, and Completed), with reward amounts and next milestone information.
  • Bug Fixes

    • Improved error handling in compliance and wallet sections to provide consistent, user-friendly error messages.
  • Chores

    • Code formatting and styling improvements across components for consistency.

@vercel
Copy link

vercel bot commented Feb 19, 2026

@KingFRANKHOOD is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

This PR introduces a "My Claims" tab to the user profile page that displays claimed bounties organized by status (Active, In Review, Completed). It adds a new MyClaims component, supporting utilities and types, integrates infinite bounty fetching, and makes widespread formatting adjustments across the codebase including quote style standardization and error handling simplification.

Changes

Cohort / File(s) Summary
My Claims Feature
app/profile/[userId]/page.tsx, components/reputation/my-claims.tsx, components/reputation/__tests__/my-claims.test.ts
New MyClaims component with status normalization, sectioning logic, and UI rendering. Integration via useInfiniteBounties hook to fetch and filter claims. Unit tests for normalizeStatus, getClaimsBySection, and claim grouping.
Bounty Detail Components
components/bounty-detail/*
Formatting and import reorganization across multiple detail card components (badges, skeleton, description, header, requirements, sidebar-cta) without functional changes.
Error Handling Simplification
components/compliance/document-upload.tsx, components/wallet/withdrawal-section.tsx
Changed error handling from typed catch with err.message to bare catch, now displaying generic failure messages regardless of error details.
Compliance & Bounty Components
components/compliance/appeal-dialog.tsx, components/bounty/sponsor-review-dashboard.tsx
Minor formatting (quote style, semicolons) and error handling changes in appeal dialog; SponsorReviewDashboard Action type updated to use double quotes and simplified catch block.
Quote Style Standardization
types/participation.ts
Converted string literal unions from single quotes to double quotes across ApplicationStatus, SubmissionStatus, ReviewStatus, MilestoneStatus, and CompetitionStatus types with multiline formatting.
Config & Mock Data
lib/bounty-config.ts, lib/mock-bounty.ts, lib/services/geo-restriction.ts, hooks/use-bounty-detail.ts
Quote style updates, reintroduced config exports, removed placeholder comments from geo-restriction, updated detectProxy signature, and minor formatting adjustments.
API & Loading States
app/api/bounties/[id]/route.ts, app/bounty/[bountyId]/loading.tsx, app/bounty/[bountyId]/not-found.tsx
Whitespace and formatting normalization; no functional logic changes.

Sequence Diagram

sequenceDiagram
    participant ProfilePage as Profile Page
    participant Hook as useInfiniteBounties
    participant API as Bounty API
    participant Util as flattenBountyPages
    participant Component as MyClaims Component

    ProfilePage->>Hook: Initialize hook with userId
    ProfilePage->>Hook: Call fetchNextPage if nextPage exists
    Hook->>API: Fetch bounties (paginated)
    API-->>Hook: Return bounty page data
    ProfilePage->>Util: Process infinite pages
    Util-->>ProfilePage: Return flattened bounties
    ProfilePage->>ProfilePage: Filter by claimedBy, normalize status → myClaims[]
    ProfilePage->>Component: Pass claims array
    Component->>Component: Group claims by status via getClaimsBySection
    Component-->>ProfilePage: Render sectioned claims UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Benjtalkshow

Poem

🐰 A claims tab hops into the profile space,
Where bounties claimed wear status with grace—
Active, In Review, Completed delight,
While quotes now double, formatted just right! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Multiple unrelated changes present: error handling refactors in compliance/document-upload, wallet/withdrawal-section; styling updates in compliance/appeal-dialog; comment removals in geo-restriction; quote style changes in types and components; and extensive formatting-only changes across many files. Separate out-of-scope changes into dedicated PRs: error handling changes, styling updates, comment removals, quote normalization, and formatting-only refactors should each be isolated from the core My Claims feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat:implement user dashboard-my claims' directly and clearly describes the main change—implementing the My Claims feature for the user dashboard.
Linked Issues check ✅ Passed All coding requirements from issue #83 are met: MyClaim type defined with correct fields, dashboard sections created (Active Claims, In Review, Completed), status filtering implemented, and opportunity detail links functional.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
components/reputation/my-claims.tsx (2)

35-43: getClaimsBySection is O(sections × statuses × claims) — fine now, but note the quadratic shape.

Each call normalizes every section status string on every claim iteration. For the current small dataset this is negligible. If the claim list ever grows large, consider pre-normalizing the section statuses into a Set once.

♻️ Pre-normalize section statuses
 export function getClaimsBySection(claims: MyClaim[]) {
     return CLAIM_SECTIONS.map((section) => ({
         section,
-        claims: claims.filter((claim) => {
-            const normalizedClaimStatus = normalizeStatus(claim.status);
-            return section.statuses.some((status) => normalizeStatus(status) === normalizedClaimStatus);
-        }),
+        claims: (() => {
+            const normalizedStatuses = new Set(section.statuses.map(normalizeStatus));
+            return claims.filter((claim) => normalizedStatuses.has(normalizeStatus(claim.status)));
+        })(),
     }));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/reputation/my-claims.tsx` around lines 35 - 43, getClaimsBySection
currently calls normalizeStatus repeatedly for every section status inside the
claims.filter loop, causing O(sections × statuses × claims) work; to fix it,
inside getClaimsBySection precompute a Set of normalized statuses for each
section (use CLAIM_SECTIONS and normalizeStatus) once before filtering (e.g.,
const normalizedStatuses = new Set(section.statuses.map(normalizeStatus))) and
then change the inner filter to check membership with
normalizedStatuses.has(normalizedClaimStatus) so each status is normalized only
once per section.

19-23: Redundant entries in CLAIM_SECTIONS after normalization.

"in-review" and "in review" both normalize to "in-review", making one redundant. Same applies if any other pairs collapse. Since getClaimsBySection normalizes both sides, you only need the canonical (already-normalized) form in each statuses array.

♻️ Suggested cleanup
 export const CLAIM_SECTIONS: { title: string; statuses: string[] }[] = [
     { title: "Active Claims", statuses: ["active", "claimed", "in-progress"] },
-    { title: "In Review", statuses: ["in-review", "in review", "review", "pending", "under-review"] },
+    { title: "In Review", statuses: ["in-review", "review", "pending", "under-review"] },
     { title: "Completed", statuses: ["completed", "closed", "accepted", "done"] },
 ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/reputation/my-claims.tsx` around lines 19 - 23, CLAIM_SECTIONS
contains duplicate-equivalent status strings because getClaimsBySection
normalizes values; update CLAIM_SECTIONS so each statuses array uses only the
canonical, normalized forms (e.g., keep "in-review" and remove "in review") to
avoid redundant entries, reviewing all entries in CLAIM_SECTIONS and ensuring
they match the normalization logic used in getClaimsBySection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/profile/`[userId]/page.tsx:
- Around line 45-69: myClaims currently treats bountyResponse?.data as empty
which hides loading/error states; update the logic around the useMemo in
page.tsx to respect bountyResponse.isLoading and bountyResponse.error (or
equivalent) instead of silently returning an empty array: detect isLoading and
make the component render a spinner/placeholder for the "My Claims" tab, detect
error and render an error message, and only compute myClaims from
bountyResponse.data when not loading and no error; reference the useMemo hook
named myClaims, the bountyResponse object, and the UI path that renders the "My
Claims" tab to wire the loading/error states into the rendered output.
- Around line 53-59: The frontend branch that maps bounty.status +
claimExpiresAt to "in-review" is out of sync with backend semantics: remove the
heuristic in app/profile/[userId]/page.tsx that sets status = "in-review" (the
code inspecting bounty.status and bounty.claimExpiresAt) and instead rely on the
backend-normalized status (or a single canonical mapper that consumes
BountyLogic.processBountyStatus() output). Ask backend team whether expired
claims are auto-reverted to "open" or should map to a review UI state; then
either (a) accept only the backend statuses ('open'|'claimed'|'closed') and
derive UI labels from them, or (b) implement a documented mapping function
(e.g., mapBackendBountyStatusToUI) that explicitly translates backend statuses
to UI states and uses server-provided processed status rather than computing
"in-review" locally.
- Line 20: The component currently calls useBounties(), which only returns the
first page and omits claims on subsequent pages; replace useBounties() with
useInfiniteBounties(), then call flattenBountyPages() on the returned pages to
produce a single list before applying the claimedBy filter (e.g., where you
currently filter bountyResponse.data by claimedBy). Update any variable names as
needed (bountyResponse → infiniteBounties/pages) and ensure you handle
loading/hasNext/fetchNext logic from useInfiniteBounties() if required.

---

Nitpick comments:
In `@components/reputation/my-claims.tsx`:
- Around line 35-43: getClaimsBySection currently calls normalizeStatus
repeatedly for every section status inside the claims.filter loop, causing
O(sections × statuses × claims) work; to fix it, inside getClaimsBySection
precompute a Set of normalized statuses for each section (use CLAIM_SECTIONS and
normalizeStatus) once before filtering (e.g., const normalizedStatuses = new
Set(section.statuses.map(normalizeStatus))) and then change the inner filter to
check membership with normalizedStatuses.has(normalizedClaimStatus) so each
status is normalized only once per section.
- Around line 19-23: CLAIM_SECTIONS contains duplicate-equivalent status strings
because getClaimsBySection normalizes values; update CLAIM_SECTIONS so each
statuses array uses only the canonical, normalized forms (e.g., keep "in-review"
and remove "in review") to avoid redundant entries, reviewing all entries in
CLAIM_SECTIONS and ensuring they match the normalization logic used in
getClaimsBySection.

Benjtalkshow

This comment was marked as off-topic.

Copy link
Contributor

@Benjtalkshow Benjtalkshow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KingFRANKHOOD

  1. CI failed because ESLint/TypeScript reported 7 errors—primarily '@typescript-eslint/no-explicit-any' in several files and a 'react-hooks/set-state-in-effect' error in components/wallet/withdrawal-section.tsx—plus multiple 'no-unused-vars' warnings and a missing useEffect dependency list.

  2. Fix by replacing any with explicit types, move or defer the synchronous setState out of the effect (or refactor the effect to avoid immediate state updates), add the missing dependencies to the useEffect array, remove unused variables, then commit and re-run the workflow.

  3. Fix the coderabbit correction. See sample:

image

@KingFRANKHOOD
Copy link
Contributor Author

@Benjtalkshow Alright. Fixing them now

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/compliance/limits-display.tsx (1)

89-93: ⚠️ Potential issue | 🟡 Minor

Add lower-bound guard to prevent negative width values.

The backend's getRemainingLimits() calculation uses (usage / limits) * 100 and is designed to prevent negative percentages; usage values start at 0 and are only incremented or reset. However, the frontend should defensively clamp both directions to protect against potential upstream anomalies or future changes. Currently, Math.min(100, percent) only guards against overflow; a negative percent would still produce invalid CSS (width: -N%), which browsers silently treat as 0 but represents an unintended state.

Proposed fix
-                    style={{ width: `${Math.min(100, percent)}%` }}
+                    style={{ width: `${Math.min(100, Math.max(0, percent))}%` }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/compliance/limits-display.tsx` around lines 89 - 93, The
progress-bar width currently uses Math.min(100, percent) which prevents overflow
but allows negative percent values; update the inline style in the component
that renders the div with className "bg-primary h-2 rounded-full transition-all"
to clamp percent into the 0–100 range (e.g., replace Math.min(100, percent) with
a clamp such as Math.max(0, Math.min(100, percent)) or a small helper like
clampPercent(percent)) so the rendered width never becomes negative or exceeds
100.
🧹 Nitpick comments (3)
lib/services/appeal.ts (1)

39-43: getUserAppeals returns raw internal references, inconsistent with the defensive-copy pattern in submitAppeal/getAppeal.

submitAppeal (line 29) and getAppeal (line 36) both return { ...appeal } to prevent callers from mutating internal state. getUserAppeals returns the raw objects from MOCK_APPEALS directly, so a caller that mutates a returned item will silently corrupt the in-memory store.

♻️ Proposed fix — map to shallow copies for consistency
  static async getUserAppeals(userId: string): Promise<VerificationAppeal[]> {
      return Object.values(MOCK_APPEALS)
          .filter(a => a.userId === userId)
-         .sort((a, b) => new Date(b.submittedAt).getTime() - new Date(a.submittedAt).getTime());
+         .sort((a, b) => new Date(b.submittedAt).getTime() - new Date(a.submittedAt).getTime())
+         .map(a => ({ ...a }));
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/appeal.ts` around lines 39 - 43, getUserAppeals currently
returns references to the objects stored in MOCK_APPEALS (unlike submitAppeal
and getAppeal which return defensive shallow copies), so callers can mutate the
in-memory store; update getUserAppeals to map the filtered/sorted results to
shallow copies (e.g., return items via {...a} or equivalent) before returning to
maintain the same defensive-copy pattern and protect MOCK_APPEALS from external
mutation.
lib/services/geo-restriction.ts (1)

62-62: Use the _ip naming convention instead of void ip; to suppress the unused-parameter warning.

void ip; is non-idiomatic in TypeScript/JavaScript. The conventional way to declare an intentionally unused parameter is to prefix it with _:

♻️ Proposed refactor
-    static async detectProxy(ip: string): Promise<boolean> {
+    static async detectProxy(_ip: string): Promise<boolean> {
         // In production: check proxy databases or use APIs
         // For now, use similar logic as VPN detection
-        void ip;
         return false;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/geo-restriction.ts` at line 62, Replace the non-idiomatic "void
ip;" statement by renaming the unused parameter "ip" to "_ip" in the
function/method in geo-restriction.ts (remove the "void ip;" line); i.e., change
the parameter declaration from ip to _ip so the unused-parameter warning is
suppressed idiomatically and no inline void statement is needed.
app/api/withdrawal/submit/route.ts (1)

24-30: Error type narrowing and safe message extraction look good.

One existing (pre-PR) issue worth noting: the catch block always returns HTTP 400 regardless of whether the error is a client mistake (bad input) or an unexpected server-side failure (e.g., service/DB unavailability). A server-side failure should return 500. Consider separating the two cases:

💡 Suggested status-code differentiation
-    } catch (error: unknown) {
+    } catch (error: unknown) {
         console.error("Error submitting withdrawal:", error);
         const message = error instanceof Error ? error.message : "Withdrawal failed";
+        // Use 400 only for client-caused errors; fall back to 500 for unexpected failures
+        const isClientError = error instanceof Error && (error as any).statusCode === 400;
         return NextResponse.json(
             { error: message },
-            { status: 400 }
+            { status: isClientError ? 400 : 500 }
         );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/withdrawal/submit/route.ts` around lines 24 - 30, The catch block in
the route handler in app/api/withdrawal/submit/route.ts currently always returns
400; change it to return 400 only for client/validation errors and 500 for
unexpected server failures: detect client errors by checking known signatures
(e.g. error instanceof Error && (error.name === 'ValidationError' || (error as
any).status === 400 || (error as any).statusCode === 400)) or by using your
app's BadRequest/ValidationError class if available, and call
NextResponse.json({ error: message }, { status: 400 }) for those cases,
otherwise log the error and return NextResponse.json({ error: message }, {
status: 500 }) for server-side failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/compliance/terms/route.ts`:
- Around line 9-11: The catch block in the GET handler in
app/api/compliance/terms/route.ts currently swallows errors; update the catch to
log the caught error (include the Error object and a clear message) before
returning the 500 response so failures inside
TermsService.getCurrentTermsVersion() are recorded (match the pattern used in
compliance/status/route.ts, e.g., console.error("Failed to fetch terms:",
error)). Ensure you reference the same handler function where
TermsService.getCurrentTermsVersion() is called and keep the response behavior
unchanged.

In `@app/api/compliance/upgrade/route.ts`:
- Around line 39-41: The GET handler in app/api/compliance/upgrade/route.ts
currently uses a bare catch that swallows exceptions from
VerificationService.getVerificationStatus(); update the catch to accept the
error (e.g., "err") and log the error with the existing logger (or
console.error) before returning the 500 NextResponse so failures are visible;
reference the GET handler and VerificationService.getVerificationStatus() when
locating the change and ensure the logged message provides context (e.g.,
"Failed to fetch verification status") along with the error object.

In `@components/compliance/appeal-dialog.tsx`:
- Around line 33-35: The catch in the AppealDialog component that handles
AppealService.submitAppeal currently forwards error.message verbatim to alert();
instead sanitize and limit what is shown by: normalize the caught error in the
catch block for AppealService.submitAppeal, extract message only if it matches a
safe whitelist/regex or otherwise use a generic "Failed to submit appeal"
fallback, strip newlines/stack traces, and truncate to a short max length (e.g.,
150–200 chars) before calling alert; update the catch (error: unknown) handling
in AppealDialog to perform these checks and transformations so internal server
details are never shown.

In `@components/compliance/document-upload.tsx`:
- Around line 43-45: The catch block that narrows error as Error and passes
error.message to setError can surface unsafe server-originated text in the UI;
change the catch in the upload flow (the try/catch around the onUpload call that
calls setError) to avoid rendering raw error.message by mapping known, safe
error cases to user-friendly messages and otherwise using a stable fallback like
"Upload failed" (while logging the original error for debugging via
console.error or a logger); update the code paths that call setError (and any UI
that reads that state) to use only the sanitized message.

In `@components/wallet/withdrawal-section.tsx`:
- Around line 46-48: The current validationError expression short-circuits when
isAmountWithinBalance is false, so users get no message when amount > balance;
add a new balanceError (e.g. const balanceError = !isAmountWithinBalance ?
'Amount exceeds available balance' : null) and keep validationError based on
validationData as-is, then render balanceError wherever validationError is shown
and include it in any combined error/Alert used by the withdrawal UI
(referencing validationError, balanceError, isAmountWithinBalance,
validationData and canWithdraw to locate the logic and button state).
- Around line 40-44: The effect calling validateWithdrawal on every keystroke
causes race conditions and hides over-balance errors; debounce parsedAmount
(e.g., with a useDebounce hook or lodash debounce) and use the debounced value
in the effect and as the argument to validateWithdrawal (update the effect deps
to [debouncedAmount, isValidAmount, isAmountWithinBalance, validateWithdrawal])
so mutations fire only after the user pauses, and change the error rendering
logic (where isValidAmount and isAmountWithinBalance are checked) to also show a
specific error when isAmountWithinBalance is false so users see why the button
is disabled; key symbols: parsedAmount, validateWithdrawal (from
useValidateWithdrawal), isValidAmount, isAmountWithinBalance.

In `@lib/services/geo-restriction.ts`:
- Around line 59-64: detectProxy currently returns false silently which hides a
compliance gap; create a tracking issue in the repo for "implement proxy
detection for compliance (OFAC risk)" and update the detectProxy function to
reflect its unimplemented status by adding an explicit TODO comment and a clear
runtime signal (e.g., log a warning and either throw a NotImplemented/Runtime
error or return null/undefined) so callers in GeoRestrictionService
(detectProxy) must handle the unknown result; include brief notes in the TODO
referencing recommended providers (IPHub, ip-api.com proxy field, IP2Proxy) and
update any caller logic to treat a non-boolean result as "unknown / require
manual review".

---

Outside diff comments:
In `@components/compliance/limits-display.tsx`:
- Around line 89-93: The progress-bar width currently uses Math.min(100,
percent) which prevents overflow but allows negative percent values; update the
inline style in the component that renders the div with className "bg-primary
h-2 rounded-full transition-all" to clamp percent into the 0–100 range (e.g.,
replace Math.min(100, percent) with a clamp such as Math.max(0, Math.min(100,
percent)) or a small helper like clampPercent(percent)) so the rendered width
never becomes negative or exceeds 100.

---

Nitpick comments:
In `@app/api/withdrawal/submit/route.ts`:
- Around line 24-30: The catch block in the route handler in
app/api/withdrawal/submit/route.ts currently always returns 400; change it to
return 400 only for client/validation errors and 500 for unexpected server
failures: detect client errors by checking known signatures (e.g. error
instanceof Error && (error.name === 'ValidationError' || (error as any).status
=== 400 || (error as any).statusCode === 400)) or by using your app's
BadRequest/ValidationError class if available, and call NextResponse.json({
error: message }, { status: 400 }) for those cases, otherwise log the error and
return NextResponse.json({ error: message }, { status: 500 }) for server-side
failures.

In `@lib/services/appeal.ts`:
- Around line 39-43: getUserAppeals currently returns references to the objects
stored in MOCK_APPEALS (unlike submitAppeal and getAppeal which return defensive
shallow copies), so callers can mutate the in-memory store; update
getUserAppeals to map the filtered/sorted results to shallow copies (e.g.,
return items via {...a} or equivalent) before returning to maintain the same
defensive-copy pattern and protect MOCK_APPEALS from external mutation.

In `@lib/services/geo-restriction.ts`:
- Line 62: Replace the non-idiomatic "void ip;" statement by renaming the unused
parameter "ip" to "_ip" in the function/method in geo-restriction.ts (remove the
"void ip;" line); i.e., change the parameter declaration from ip to _ip so the
unused-parameter warning is suppressed idiomatically and no inline void
statement is needed.

Comment on lines 59 to 64
static async detectProxy(ip: string): Promise<boolean> {
// In production: check proxy databases or use APIs
// For now, use similar logic as VPN detection
void ip;
return false;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check git history for the file
echo "=== Recent commits affecting geo-restriction.ts ==="
git log --oneline --all -20 -- lib/services/geo-restriction.ts

echo -e "\n=== Current detectProxy method ==="
sed -n '59,64p' lib/services/geo-restriction.ts

echo -e "\n=== Check for VPN_INDICATORS in current file ==="
rg "VPN_INDICATORS" lib/services/geo-restriction.ts || echo "VPN_INDICATORS not found in current file"

echo -e "\n=== Check entire codebase for VPN_INDICATORS ==="
rg "VPN_INDICATORS" --type ts --type js || echo "VPN_INDICATORS not found in codebase"

Repository: boundlessfi/bounties

Length of output: 669


🏁 Script executed:

#!/bin/bash
# Check the most recent changes to understand context
echo "=== Recent diff for geo-restriction.ts ==="
git diff HEAD~5 HEAD -- lib/services/geo-restriction.ts | head -150

echo -e "\n=== Full content of geo-restriction.ts ==="
wc -l lib/services/geo-restriction.ts

Repository: boundlessfi/bounties

Length of output: 232


🏁 Script executed:

#!/bin/bash
# Check the recent commits in detail
echo "=== Commit 6d6fe4c (KYC/compliance changes) ==="
git show 6d6fe4c --stat | head -50

echo -e "\n=== Diff of geo-restriction.ts in commit 6d6fe4c ==="
git show 6d6fe4c -- lib/services/geo-restriction.ts | head -200

echo -e "\n=== Check git log with patch for VPN_INDICATORS ==="
git log --all -p -- lib/services/geo-restriction.ts | grep -A 5 -B 5 "VPN_INDICATORS" | head -100

Repository: boundlessfi/bounties

Length of output: 8359


🏁 Script executed:

#!/bin/bash
# Read full file to see context
echo "=== Full geo-restriction.ts file ==="
cat -n lib/services/geo-restriction.ts

Repository: boundlessfi/bounties

Length of output: 4795


detectProxy is a non-functional stub in a compliance-sensitive service — track this as a known gap.

This method always returns false, meaning users routing through proxies or anonymizing services will never be flagged as isProxy = true. For a service enforcing OFAC sanctions (Cuba, Iran, North Korea, Syria), an undetected proxy is a realistic bypass vector.

The stub implementation is concerning precisely because it remains incomplete without any heuristic fallback. Recommend opening a tracking issue so this gap doesn't get lost, and consider adding an explicit TODO comment that makes the stub status clear rather than allowing it to appear as working logic. In production, services like IPHub, ip-api.com (proxy field), or IP2Proxy can fill this role.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/geo-restriction.ts` around lines 59 - 64, detectProxy currently
returns false silently which hides a compliance gap; create a tracking issue in
the repo for "implement proxy detection for compliance (OFAC risk)" and update
the detectProxy function to reflect its unimplemented status by adding an
explicit TODO comment and a clear runtime signal (e.g., log a warning and either
throw a NotImplemented/Runtime error or return null/undefined) so callers in
GeoRestrictionService (detectProxy) must handle the unknown result; include
brief notes in the TODO referencing recommended providers (IPHub, ip-api.com
proxy field, IP2Proxy) and update any caller logic to treat a non-boolean result
as "unknown / require manual review".

KingFRANKHOOD added a commit to KingFRANKHOOD/bounties that referenced this pull request Feb 20, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
app/api/compliance/upgrade/route.ts (1)

20-26: error.message from the service layer is still forwarded to the client in the POST error response.

The errors explicitly thrown in VerificationService.createVerificationRequest are user-friendly domain errors ("You already have a pending verification request", "Target tier must be higher than current tier"). However, this pattern is vulnerable to exposure of internal error details from downstream service calls like ComplianceService.getUserCompliance() or ComplianceService.updateVerificationStatus(), or from unexpected async failures. For consistency with appeal-dialog.tsx, which uses a fixed fallback message without exposing error.message, consider using the safer pattern:

🛡️ Suggested safer pattern
-    } catch (error: unknown) {
-        console.error("Error creating verification request:", error);
-        const message = error instanceof Error ? error.message : "Failed to create verification request";
-        return NextResponse.json(
-            { error: message },
-            { status: 400 }
-        );
+    } catch (error: unknown) {
+        console.error("Error creating verification request:", error);
+        return NextResponse.json(
+            { error: "Failed to create verification request" },
+            { status: 400 }
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/compliance/upgrade/route.ts` around lines 20 - 26, The catch block
currently returns error.message to the client for failures from
VerificationService.createVerificationRequest (and downstream calls like
ComplianceService.getUserCompliance /
ComplianceService.updateVerificationStatus), which can leak internal details;
change it to always respond with a fixed, user-safe message (e.g., "Failed to
create verification request") while keeping the full error logged server-side
(console.error or processLogger) for debugging, and only expose specific domain
errors if you have a safe explicit whitelist/type (e.g., a custom DomainError
type) — update the catch in route.ts around the POST handler to implement this
pattern for VerificationService.createVerificationRequest and downstream
failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/profile/`[userId]/page.tsx:
- Around line 59-72: The "In Review" section is unreachable because myClaims
(built in the useMemo in page.tsx) only maps backend statuses to "open",
"claimed", or "completed", which never normalizes to the "in-review" variants
expected by CLAIM_SECTIONS/normalizeStatus; update the mapping in the myClaims
creation (the mapping inside useMemo that uses
flattenBountyPages(infiniteBounties?.pages)) to emit an "in-review" status when
appropriate (for example map a backend review indicator or a specific bounty
field such as bounty.reviewStatus or a combination like bounty.status ===
"claimed" && bounty.isUnderReview to "in-review"), or alternatively remove the
"In Review" entry from CLAIM_SECTIONS in components/reputation/my-claims.tsx so
the UI and backend statuses align. Ensure you reference myClaims,
flattenBountyPages, infiniteBounties, CLAIM_SECTIONS, and normalizeStatus when
making the change.
- Around line 21-34: The current logic auto-pages through all bounties
client-side by calling useInfiniteBounties() with no filters and then filtering
claimedBy === userId, causing unbounded fetches; update the code to avoid
fetching every page: either (a) modify useInfiniteBounties()/the underlying
query to accept a claimedBy/userId parameter so server-side filtering returns
only the user's claimed bounties (update any BountyListParams and server handler
accordingly), or if server changes aren’t yet possible, implement a client-side
safety cap—stop auto-fetching after N pages or remove the automatic
fetchNextPage in useEffect and fetch only a single page or limited pages
(useInfiniteBounties, fetchNextPage, hasNextPage, isFetchingNextPage) to prevent
unbounded pagination and the full skeleton blocking the UI.

In `@components/wallet/withdrawal-section.tsx`:
- Around line 80-84: canWithdraw can become true before the new debounced amount
is validated; update the canWithdraw logic to require the debounce to be settled
and an explicit successful validation result (and no validation in-flight)
before enabling submit. Specifically, modify the canWithdraw predicate that
currently references isValidAmount, isAmountWithinBalance, validationError, and
complianceData to also require debounceSettled && validationData?.valid === true
&& !validationMutation?.isLoading (or the equivalent isValidating flag) so the
button stays disabled during the debounce window and while a validation request
is pending.

---

Duplicate comments:
In `@app/api/compliance/upgrade/route.ts`:
- Around line 30-42: The GET route's previous issues are resolved: the handler
uses a no-argument signature GET() and the catch now logs the error; approve and
merge as-is. No code changes required—keep the GET() function and the
console.error("Error fetching verification status:", error) in the catch block,
and proceed to merge the change.

In `@components/compliance/appeal-dialog.tsx`:
- Around line 33-34: The catch block after calling AppealService.submitAppeal
swallows errors and only shows an alert; update the catch in the submit handler
for AppealDialog to log the error for developer observability by calling
console.error with a clear context string (e.g., "AppealService.submitAppeal
failed") and the caught error object before showing the existing alert, ensuring
you reference the submit handler that calls AppealService.submitAppeal so the
log is colocated with the existing alert.

In `@components/wallet/withdrawal-section.tsx`:
- Line 17: The debounce fix using useDebounce on parsedAmount and wiring the
effect to the debounced-derived booleans (so walletInfo.balance changes are
picked up) looks correct and addresses the stale mutation/validationData race;
keep validateWithdrawal triggered from the effect using the debounced value (and
ensure any mutation handlers guard against overwriting validationData by
validating request identity if present). Also remove the duplicated
review/comment block that was repeated for lines 40-48 so the file has only one
approval comment.

In `@lib/services/geo-restriction.ts`:
- Around line 59-65: The detectProxy method currently uses a non-idiomatic "void
ip" to silence the unused parameter and silently returns false; change the
parameter name from ip to _ip (e.g., detectProxy(_ip: string)) to indicate it is
intentionally unused and remove the void statement, and add a runtime warning
(e.g., console.warn('[GeoRestriction] detectProxy: not implemented — defaulting
to false')) inside detectProxy so callers/operators see that proxy detection is
not yet implemented while preserving the Promise<boolean> return of false.

---

Nitpick comments:
In `@app/api/compliance/upgrade/route.ts`:
- Around line 20-26: The catch block currently returns error.message to the
client for failures from VerificationService.createVerificationRequest (and
downstream calls like ComplianceService.getUserCompliance /
ComplianceService.updateVerificationStatus), which can leak internal details;
change it to always respond with a fixed, user-safe message (e.g., "Failed to
create verification request") while keeping the full error logged server-side
(console.error or processLogger) for debugging, and only expose specific domain
errors if you have a safe explicit whitelist/type (e.g., a custom DomainError
type) — update the catch in route.ts around the POST handler to implement this
pattern for VerificationService.createVerificationRequest and downstream
failures.

Comment on lines 21 to 34
const {
data: infiniteBounties,
isLoading: isBountiesLoading,
isError: isBountiesError,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteBounties();

useEffect(() => {
if (hasNextPage && !isFetchingNextPage) {
void fetchNextPage();
}
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "BountyListParams|claimedBy|claimed_by" --type ts -A 15 -B 2

Repository: boundlessfi/bounties

Length of output: 25951


Fetching all bounties client-side to filter by user is a major performance issue.

useInfiniteBounties() is called without parameters, so the useEffect waterfall fetches every page of every bounty in the system and then client-side-filters by claimedBy === userId (line 63). This makes the "My Claims" tab's load time and memory footprint grow linearly with the total bounty count, and the full skeleton loading state (line 193) blocks the UI until the last page lands.

Unfortunately, BountyListParams does not currently support a claimedBy or user-filter parameter—the interface only supports status, type, difficulty, projectId, tags, and search. Server-side filtering by claimed bounties would require extending the API before this can be optimized. As a workaround, consider implementing pagination limits or a request timeout to prevent the load from becoming prohibitive as the bounty dataset grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/profile/`[userId]/page.tsx around lines 21 - 34, The current logic
auto-pages through all bounties client-side by calling useInfiniteBounties()
with no filters and then filtering claimedBy === userId, causing unbounded
fetches; update the code to avoid fetching every page: either (a) modify
useInfiniteBounties()/the underlying query to accept a claimedBy/userId
parameter so server-side filtering returns only the user's claimed bounties
(update any BountyListParams and server handler accordingly), or if server
changes aren’t yet possible, implement a client-side safety cap—stop
auto-fetching after N pages or remove the automatic fetchNextPage in useEffect
and fetch only a single page or limited pages (useInfiniteBounties,
fetchNextPage, hasNextPage, isFetchingNextPage) to prevent unbounded pagination
and the full skeleton blocking the UI.

Comment on lines 59 to 72
const myClaims = useMemo<MyClaim[]>(() => {
const bounties = flattenBountyPages(infiniteBounties?.pages);

return bounties
.filter((bounty) => bounty.claimedBy === userId)
.map((bounty) => {
return {
bountyId: bounty.id,
title: bounty.issueTitle,
status: bounty.status === "closed" ? "completed" : bounty.status,
rewardAmount: bounty.rewardAmount ?? undefined,
};
});
}, [infiniteBounties?.pages, userId]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "getClaimsBySection|normalizeStatus|CLAIM_SECTIONS" --type ts --type tsx -A 15 -B 2

Repository: boundlessfi/bounties

Length of output: 91


🏁 Script executed:

rg -n "getClaimsBySection|normalizeStatus|CLAIM_SECTIONS" -A 15 -B 2

Repository: boundlessfi/bounties

Length of output: 7811


The "In Review" section will be unreachable with the current status mapping.

The code in page.tsx (lines 59-72) transforms bounty statuses with only one rule: "closed" → "completed". Since backend statuses are limited to 'open' | 'claimed' | 'closed', the resulting MyClaim statuses will be "open", "claimed", or "completed".

However, CLAIM_SECTIONS in components/reputation/my-claims.tsx defines the "In Review" section with expected statuses: ["in-review", "in review", "review", "pending", "under-review"]. The normalizeStatus() function will normalize the page.tsx statuses to "open", "claimed", and "completed" — none of which match the "In Review" section's expected values.

This means the "In Review" section will always render empty. Either:

  • Add status transformations in page.tsx to produce "in-review" (e.g., map a backend status or field to this value), or
  • Remove "In Review" from CLAIM_SECTIONS if it's not needed for the current backend structure.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/profile/`[userId]/page.tsx around lines 59 - 72, The "In Review" section
is unreachable because myClaims (built in the useMemo in page.tsx) only maps
backend statuses to "open", "claimed", or "completed", which never normalizes to
the "in-review" variants expected by CLAIM_SECTIONS/normalizeStatus; update the
mapping in the myClaims creation (the mapping inside useMemo that uses
flattenBountyPages(infiniteBounties?.pages)) to emit an "in-review" status when
appropriate (for example map a backend review indicator or a specific bounty
field such as bounty.reviewStatus or a combination like bounty.status ===
"claimed" && bounty.isUnderReview to "in-review"), or alternatively remove the
"In Review" entry from CLAIM_SECTIONS in components/reputation/my-claims.tsx so
the UI and backend statuses align. Ensure you reference myClaims,
flattenBountyPages, infiniteBounties, CLAIM_SECTIONS, and normalizeStatus when
making the change.

@KingFRANKHOOD
Copy link
Contributor Author

Hi @Benjtalkshow, Please review for any corrections

Copy link
Contributor

@Benjtalkshow Benjtalkshow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @KingFRANKHOOD ,

Thanks for the modification. There are just a few things to take care of:

  1. Please fix the merge conflicts.
  2. Make sure your code builds successfully and is lint-error free (I’ve updated the codebase, so it now compiles fully).
  3. Fix coderabbit corrections
  4. Attach screenshot of your implementation
  5. Pull the latest changes from main before pushing your updates.Thanks again for your contribution — I really appreciate your efforts!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
hooks/Use-bounty-detail.ts (1)

1-3: Filename Use-bounty-detail.ts deviates from the lowercase hook convention and creates a portability hazard.

All sibling hooks in hooks/ follow the use-* lowercase naming convention (e.g., use-bounties.ts, use-bounty-mutations.ts). The capital U in this filename is an exception. While the current import in components/bounty-detail/bounty-detail-client.tsx correctly matches the actual filename casing, this deviation is risky: a developer expecting the convention might write use-bounty-detail (lowercase), which would fail on case-sensitive Linux filesystems (CI/Docker) while succeeding on macOS, causing hard-to-debug build failures.

Rename the file to use-bounty-detail.ts to align with the established convention and update the one import accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/Use-bounty-detail.ts` around lines 1 - 3, The file name
Use-bounty-detail.ts uses a capital U and breaks the lowercase hook naming
convention which can cause case-sensitive filesystem failures; rename the file
to use-bounty-detail.ts and update its import in
components/bounty-detail/bounty-detail-client.tsx to match the new lowercase
filename so that the exported hook (used via bountyKeys and bountiesApi/Bounty
in the file) is resolved consistently across platforms.
lib/mock-bounty.ts (1)

44-79: Add mock entries for "in-review" and "completed" statuses to cover all My Claims sections.

Only bounty id "2" is associated with "user-1" and it only populates the "Active Claims" bucket (status: "claimed"). The "In Review" and "Completed" dashboard sections have no testable data for this user, which is consistent with the empty-state screenshot but limits local development and visual testing of those panels.

♻️ Suggested mock additions

Add two more entries to mockBounties — one for each missing section — so all three My Claims panels can be exercised locally:

   {
     id: "2",
     ...
     status: "claimed",
     claimedBy: "user-1",
     ...
   },
+  {
+    id: "12",
+    type: "feature",
+    issueTitle: "Implement notification preferences",
+    projectId: "boundless",
+    projectName: "Boundless",
+    projectLogoUrl: "/logo.svg",
+    githubRepo: "boundlessfi/bounties",
+    githubIssueUrl: "https://github.com/boundlessfi/bounties/issues/12",
+    issueNumber: 12,
+    description: "Add user notification preference settings.",
+    rewardAmount: 250,
+    rewardCurrency: "USDC",
+    claimingModel: "single-claim",
+    difficulty: "beginner",
+    tags: ["notifications", "settings"],
+    status: "in-review",
+    claimedBy: "user-1",
+    claimedAt: "2026-01-20T00:00:00Z",
+    createdAt: "2026-01-10T08:00:00Z",
+    updatedAt: "2026-02-01T11:00:00Z",
+  },
+  {
+    id: "13",
+    type: "bug",
+    issueTitle: "Fix broken avatar upload flow",
+    projectId: "boundless",
+    projectName: "Boundless",
+    projectLogoUrl: "/logo.svg",
+    githubRepo: "boundlessfi/bounties",
+    githubIssueUrl: "https://github.com/boundlessfi/bounties/issues/13",
+    issueNumber: 13,
+    description: "Avatar upload silently fails on large files.",
+    rewardAmount: 175,
+    rewardCurrency: "USDC",
+    claimingModel: "single-claim",
+    difficulty: "beginner",
+    tags: ["bug", "upload", "avatar"],
+    status: "completed",
+    claimedBy: "user-1",
+    claimedAt: "2025-12-01T00:00:00Z",
+    createdAt: "2025-11-15T08:00:00Z",
+    updatedAt: "2026-01-10T11:00:00Z",
+  },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mock-bounty.ts` around lines 44 - 79, Add two additional objects to the
mockBounties array so My Claims covers all panels: create one entry with id "3"
(status: "in-review", claimedBy: "user-1", claimedAt set to a recent ISO date)
and one entry with id "4" (status: "completed", claimedBy: "user-1", claimedAt
and a completed/closed or updatedAt ISO date). Keep other fields (type,
projectId/projectName, issueTitle, rewardAmount, difficulty, tags,
githubIssueUrl, issueNumber, etc.) consistent with existing entries so
components rendering mockBounties can display data for the "In Review" and
"Completed" sections.
components/bounty/sponsor-review-dashboard.tsx (1)

27-34: useEffect permanently suppresses server-authoritative status updates for existing items.

currIdMap.get(sub.submissionId) ?? sub unconditionally preserves the locally-held item regardless of what the incoming submissions prop says. If the parent re-fetches and supplies a server-confirmed status that differs from the current local (possibly stale) state, it will be silently discarded. This is fine for preventing flicker during an in-flight action, but it also means the component can drift permanently out of sync with the server once any item enters its local state map.

Consider restricting local preference to items that are actually in a loading state:

♻️ Proposed refactor
 React.useEffect(() => {
   setItems((curr) => {
     const currIdMap = new Map(curr.map((it) => [it.submissionId, it]));
+    const inflightIds = new Set(Object.keys(loadingIds));
     return submissions.map(
-      (sub) => (currIdMap.get(sub.submissionId) ?? sub) as ReviewSubmission,
+      (sub) =>
+        (inflightIds.has(sub.submissionId)
+          ? currIdMap.get(sub.submissionId)
+          : undefined) ?? sub,
     );
   });
 }, [submissions]);

Note: loadingIds would need to be added to the dependency array if used inside the effect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty/sponsor-review-dashboard.tsx` around lines 27 - 34, The
current useEffect in which setItems merges submissions into local state (using
currIdMap.get(sub.submissionId) ?? sub) always favors the local item and thus
can permanently ignore server-updated statuses; change the merge so it prefers
the incoming server-provided sub unless the local item is actively loading —
i.e., when mapping submissions, check currIdMap.get(sub.submissionId) and only
return the local item when its submissionId is present in a loadingIds
set/array, otherwise return the server sub (and add loadingIds to the effect
dependency array); update references to useEffect, setItems, currIdMap,
submissions, loadingIds, and ReviewSubmission accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Around line 38-56: The updater is mutating external variable prevItem inside
the setItems updater, which breaks rollback in React Strict Mode; instead,
capture the original item from the current items snapshot before calling
setItems (e.g., const prevItem = items.find(it => it.submissionId === id)), then
call setItems with a pure updater that only returns the new array (no side
effects). Use that captured prevItem for the catch rollback logic so rollback
restores the true previous ReviewSubmission, and reference the existing symbols
id, action, setItems, items, and ReviewSubmission to locate and update the code.

---

Nitpick comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Around line 27-34: The current useEffect in which setItems merges submissions
into local state (using currIdMap.get(sub.submissionId) ?? sub) always favors
the local item and thus can permanently ignore server-updated statuses; change
the merge so it prefers the incoming server-provided sub unless the local item
is actively loading — i.e., when mapping submissions, check
currIdMap.get(sub.submissionId) and only return the local item when its
submissionId is present in a loadingIds set/array, otherwise return the server
sub (and add loadingIds to the effect dependency array); update references to
useEffect, setItems, currIdMap, submissions, loadingIds, and ReviewSubmission
accordingly.

In `@hooks/Use-bounty-detail.ts`:
- Around line 1-3: The file name Use-bounty-detail.ts uses a capital U and
breaks the lowercase hook naming convention which can cause case-sensitive
filesystem failures; rename the file to use-bounty-detail.ts and update its
import in components/bounty-detail/bounty-detail-client.tsx to match the new
lowercase filename so that the exported hook (used via bountyKeys and
bountiesApi/Bounty in the file) is resolved consistently across platforms.

In `@lib/mock-bounty.ts`:
- Around line 44-79: Add two additional objects to the mockBounties array so My
Claims covers all panels: create one entry with id "3" (status: "in-review",
claimedBy: "user-1", claimedAt set to a recent ISO date) and one entry with id
"4" (status: "completed", claimedBy: "user-1", claimedAt and a completed/closed
or updatedAt ISO date). Keep other fields (type, projectId/projectName,
issueTitle, rewardAmount, difficulty, tags, githubIssueUrl, issueNumber, etc.)
consistent with existing entries so components rendering mockBounties can
display data for the "In Review" and "Completed" sections.

Comment on lines +38 to +56
let prevItem: ReviewSubmission | undefined;

setItems(curr => curr.map(it => {
if (it.submissionId === id) {
prevItem = it
return {
...it,
status: action === 'approve' ? 'approved' : action === 'reject' ? 'rejected' : 'revision_requested'
setItems((curr) =>
curr.map((it) => {
if (it.submissionId === id) {
prevItem = it;
return {
...it,
status:
action === "approve"
? "approved"
: action === "reject"
? "rejected"
: "revision_requested",
};
}
}
return it
}))
return it;
}),
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Side effect inside setItems updater breaks rollback in React Strict Mode.

prevItem is captured inside the setItems updater on Line 43. In React 19 Strict Mode (development), React invokes updater functions twice to detect side effects — the second invocation receives the result of the first (already-optimistically-mutated) state, so prevItem ends up holding the updated item rather than the original. The catch branch then "rolls back" to the optimistically-updated value, making error recovery silently a no-op in development.

Fix by reading prevItem from the items state snapshot captured in the closure before setItems is called:

🛡️ Proposed fix
 const handleAction = async (id: string, action: Action) => {
   setLoadingIds((s) => ({ ...s, [id]: true }));
-  let prevItem: ReviewSubmission | undefined;
+  const prevItem = items.find((it) => it.submissionId === id);

-  setItems((curr) =>
-    curr.map((it) => {
-      if (it.submissionId === id) {
-        prevItem = it;
-        return {
-          ...it,
-          status:
-            action === "approve"
-              ? "approved"
-              : action === "reject"
-                ? "rejected"
-                : "revision_requested",
-        };
-      }
-      return it;
-    }),
-  );
+  setItems((curr) =>
+    curr.map((it) =>
+      it.submissionId === id
+        ? {
+            ...it,
+            status:
+              action === "approve"
+                ? "approved"
+                : action === "reject"
+                  ? "rejected"
+                  : "revision_requested",
+          }
+        : it,
+    ),
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let prevItem: ReviewSubmission | undefined;
setItems(curr => curr.map(it => {
if (it.submissionId === id) {
prevItem = it
return {
...it,
status: action === 'approve' ? 'approved' : action === 'reject' ? 'rejected' : 'revision_requested'
setItems((curr) =>
curr.map((it) => {
if (it.submissionId === id) {
prevItem = it;
return {
...it,
status:
action === "approve"
? "approved"
: action === "reject"
? "rejected"
: "revision_requested",
};
}
}
return it
}))
return it;
}),
);
const prevItem = items.find((it) => it.submissionId === id);
setItems((curr) =>
curr.map((it) =>
it.submissionId === id
? {
...it,
status:
action === "approve"
? "approved"
: action === "reject"
? "rejected"
: "revision_requested",
}
: it,
),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty/sponsor-review-dashboard.tsx` around lines 38 - 56, The
updater is mutating external variable prevItem inside the setItems updater,
which breaks rollback in React Strict Mode; instead, capture the original item
from the current items snapshot before calling setItems (e.g., const prevItem =
items.find(it => it.submissionId === id)), then call setItems with a pure
updater that only returns the new array (no side effects). Use that captured
prevItem for the catch rollback logic so rollback restores the true previous
ReviewSubmission, and reference the existing symbols id, action, setItems,
items, and ReviewSubmission to locate and update the code.

@KingFRANKHOOD
Copy link
Contributor Author

Hi @KingFRANKHOOD ,

Thanks for the modification. There are just a few things to take care of:

  1. Please fix the merge conflicts.
  2. Make sure your code builds successfully and is lint-error free (I’ve updated the codebase, so it now compiles fully).
  3. Fix coderabbit corrections
  4. Attach screenshot of your implementation
  5. Pull the latest changes from main before pushing your updates.Thanks again for your contribution — I really appreciate your efforts!

I believe I have fixed it all, Please review and I'm open to correct whatever needs to be corrected

Copy link
Contributor

@Benjtalkshow Benjtalkshow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@Benjtalkshow Benjtalkshow merged commit 39ddd3c into boundlessfi:main Feb 21, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement User Dashboard – My Claims

2 participants